#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 时间     : 2020/06/11 20:48
# 作者     : DL (584054432@qq.com)
# 网址     : https://gitee.com/dingliangPython/
# 软件版本 : Python3.7.3
# 功能     ：
import os
import re
import sys

import connect_database
import loadLog
import timeEXT
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QThread
from PyQt5.QtWidgets import QApplication, QDialog
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QTableView, QTableWidgetItem
from pyqt5_common import APP_CONFIG_PATH, APP_NAME, show_message

import s7config_window
import sdk_config_window
from configUi import Ui_Form
import gcl_sdk
import logging
import s7


class S7Thread(QThread):
    """docstring for S7Thread"""
    read_config_signal = pyqtSignal(list)
    start_signal = pyqtSignal(bool)
    start_flag = None
    clinet = None

    def __init__(self, config, s7_read_signal = None, flag = None):
        super(S7Thread, self).__init__()
        self.config = config
        self.s7_read_signal = s7_read_signal
        self.start_flag = flag
        self.read_config = []
        self.name = self.config[0]
        self.log = logging.getLogger("main.plc_thread")

        self.client = s7.S7Client()
        self.read_config_signal.connect(self.set_read_config)
        self.start_signal.connect(self.set_start_flag)

    # 设置启停标志
    def set_start_flag(self, value):
        self.start_flag = value
        if value and not self.isRunning():
            self.start()
        if not value:
            if self.client and self.client.get_connected():
                self.client.destroy()

    def set_read_config(self, config):
        self.read_config = config

    def connect(self):
        if self.client and self.client.get_connected():
            return
        try:
            (name, host, port, rack, slot, tsap) = self.config
            if rack and slot:
                self.client.connect(host, rack = int(rack), slot = int(slot), tcpport = int(port))
            else:
                tsap = tsap.replace('.', '')
                self.client.set_connection_params(host, local_tsap = 0x1010, remote_tsap = int(tsap, 16))
                self.client.connect(host, rack = 0, slot = 0, tcpport = int(port))

        except Exception as e:
            info = "PLC客户端:{},连接失败,异常:{}".format(self.name, e)
            self.log.error(info)
            # show_message(info)

    def run(self):

        if not self.start_flag:
            self.log.warning("停止休眠PLC连接:{}".format(self.name))
            return

        self.connect()
        if self.client.get_connected():
            self.log.info("PLC客户端:{},连接PLC成功....".format(self.name))
            self.run_s7_task()
        # else:
        #     self.log.info("PLC客户端:{},没有在运行...".format(self.name))

    # 运行s7任务
    def run_s7_task(self):
        try:
            while True:
                QThread.sleep(1)
                if not self.client.get_connected():
                    self.check_connect()
                    QThread.sleep(5)
                    continue

                if not self.read_config:
                    #self.log.warning("地址列错误或为空，没法读取")
                    QThread.sleep(1)
                    continue

                value_dice = {}
                for row in self.read_config:
                    data = self.client.read_value(row)
                    if data is None:
                        continue
                    value_dice.update({row.get('addr'): data})
                self.s7_read_signal.emit({self.name: value_dice})


        except Exception as e:
            self.log.info("解析或读出异常：{}".format(e))

    # 读取数据
    def read(self, config):
        param = self.client.get_read_param(config)
        data = self.client.read_area(*param[:4])
        value = self.client.parse_data(data, param)
        return value

    # 写数据
    def write(self, config, value):
        return self.client.write_data(config,value)

    # 检测连接
    def check_connect(self, e = "连接失败"):
        status = None
        for i in range(30):
            status = self.client.get_connected()
            if not status:
                self.sleep(1)
                self.log.warning("第{}次,检测PLC客户端:{},连接:{},失败".format(i + 1, self.name, status))
            else:
                status = True
                break
        if status:
            self.log.warning("重新检测PLC连接成功,启动读取线程")
            self.start()
        else:
            self.log.warning("PLC客户端:{},检测连接30秒,连接依然失败:{},检测PLC连接,10秒后退出程序....".format(self.name, e))
            self.sleep(10)
            os._exit(0)


class Window(QDialog):
    """docstring for Window"""
    app_name_signal = pyqtSignal(str)
    s7_rt_value_signal = pyqtSignal(dict)

    password_enable = None
    sdk = None
    s7_clinet_dict = {}

    def __init__(self, log_callback = None):
        super(Window, self).__init__()
        self.ui = Ui_Form()
        self.ui.setupUi(self)

        # self.log_callback = log_callback
        self.loacl_connect = self.get_local_connect()
        # 日志显示
        self.log_config = self.get_log_config()
        # self.log = loadLog.Logger(self.log_config, 'main', log_callback)
        self.log = loadLog.LoadLog(self.log_config, 'main',callback = log_callback)

        self.s7_window = s7config_window.Window()
        self.sdk_config_window = sdk_config_window.Window(self.ui.start_sdk.isChecked())

        # 不能编辑
        self.ui.uTable.setEditTriggers(QTableView.NoEditTriggers)
        self.load_local_config()
        self.connectList = self.get_conn_list()
        self.ui.log_level.currentIndexChanged.connect(self.log_level_changed)
        self.load_s7_connect()

    def load_s7_connect(self):
        if not self.s7_window.config:
            return
        for key, values in self.s7_window.config.items():
            th = S7Thread(values, self.s7_rt_value_signal, self.ui.start_s7.isChecked())
            th.start()
            self.s7_clinet_dict[key] = th

    # 启停SDK
    @pyqtSlot(int)
    def on_start_sdk_stateChanged(self, value):
        self.set_app_config("StartSDK", 2 if value else 0)
        config_path = os.path.join(APP_CONFIG_PATH, APP_NAME)
        if value:
            self.sdk = gcl_sdk.GCL_SDK(config_path=config_path)
            self.log.info("启动sdk")
            self.sdk_config_window.sdk_start_signal.emit(True)
        else:
            if self.sdk and show_message("关闭SDK,需要关闭程序,是否退出程序"):
                self.sdk.uninit()
                self.sdk = None
                self.log.info("停止sdk")

    # 启停密码
    @pyqtSlot(int)
    def on_start_password_stateChanged(self, value):
        self.set_app_config("EnablePassword", 1 if value else 0)
        self.password_enable = value
        self.log.info('启用或停用密码,状态:{}'.format('启用' if value else '停用'))

    # 获取本地配置数据库
    def get_local_connect(self):
        config_path = os.path.join(APP_CONFIG_PATH, APP_NAME, 'config.db')
        if not os.path.isfile(config_path):
            show_message("请检测配置文件:{},是否存在?".format(config_path))
            os._exit(0)

        config = {'databasepath': config_path, 'type': 'sqlite'}
        connect = connect_database.Connect(**config)
        return connect

    def closeEvent(self, event):
        """
        重写closeEvent方法，实现dialog窗体关闭时执行一些代码
        :param event: close()触发的事件
        :return: None
        """
        reply = QMessageBox.question(self, '配置', "配置改变重启程序才能生效，是否退出程序", QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            os._exit(0)
        else:
            self.hide()

    # 启动s7
    @pyqtSlot(bool)
    def on_start_s7_clicked(self, v):
        if v and os.path.exists('./snap7.dll'):
            self.set_app_config("StartS7", 1)
            self.log.info("启动S7")
            for key, thread in self.s7_clinet_dict.items():
                thread.start_signal.emit(True)
        else:
            self.set_app_config("StartS7", 0)
            self.ui.start_s7.setChecked(False)
            self.log.info("停止S7")
            for key, thread in self.s7_clinet_dict.items():
                thread.start_signal.emit(False)
        # # 检测配置
        # if not self.s7_clinet_dict:
        #     info = "没有s7配置,请检测s7配置..."
        #     show_message(info)
        #     self.log.warning(info)
        #     return
        # for key, thread in self.s7_clinet_dict.items():
        #     print(thread, )
        #     print(dir(thread))
        #     print(key,thread.isRunning())

    # 显示S7配置窗口
    @pyqtSlot(bool)
    def on_show_s7_config_clicked(self):
        self.s7_window.hide()
        self.s7_window.show()

    # 显示sdk配置窗口
    @pyqtSlot(bool)
    def on_show_sdk_config_clicked(self):
        self.sdk_config_window.hide()
        self.sdk_config_window.show()

    # 设置端口
    def on_uDBType_currentTextChanged(self, database_type):
        if database_type == "Oracle":
            self.ui.uDBPort.setText('1521')
        elif database_type == "SQLServer":
            self.ui.uDBPort.setText('1433')
        elif database_type == "Mysql":
            self.ui.uDBPort.setText('3306')
        elif database_type == "Sqlite":
            self.ui.uDBPort.clear()
            self.ui.uDBPassword.clear()
            self.ui.uDBUser.clear()
            self.ui.uDBName.clear()
            self.ui.uDSN.clear()
        elif database_type == "Access":
            self.ui.uDBHost.clear()
            self.ui.uDBPort.clear()
            self.ui.uDBName.clear()
            self.ui.uDSN.clear()

    # 日志等级变化
    def log_level_changed(self, value):
        value = (value + 1) * 10
        sql = "UPDATE LogConfig SET LogLevel='{}'".format(value)
        self.loacl_connect.execSql(sql)
        self.log.info("设置显示UI日志等级= {}".format(value))
        for handler in self.log.logger.handlers:
            if isinstance(handler, loadLog.QtHandler):
                handler.setLevel(value)
                return

    # 获取连接数据显示
    def get_conn_list(self):
        data = self.loacl_connect.loadSql("SELECT * FROM ConnectConfig ", 1)
        connectList = {}
        if not data:
            self.log.warning("请先增加数据库连接，没有查询到已配置的数据库")
            self.ui.uTable.setRowCount(0)
            return
        rows = len(data)
        self.ui.uTable.setRowCount(rows)
        self.ui.uTable.setColumnCount(len(data[0]))
        keys = list(data[0].keys())
        head = ['连接名', '源/目的', '库类型', '模式', 'IP', '端口', '用户', '密码', '库/实例', 'dsn', '初始连接数', '最大数', '空闲数', '共享数', '库路径',
                '时间格式',
                '左侧', '右侧', "sql版本"]
        self.ui.uTable.setHorizontalHeaderLabels(head)

        for i, row in enumerate(data):
            connectList.update({row.get("name"): row})  # 存储连接
            self.ui.uTable.setItem(i, 0, QTableWidgetItem(str(row.get("name"))))
            self.ui.uTable.setItem(i, 1, QTableWidgetItem(str(row.get("TagSource"))))
            self.ui.uTable.setItem(i, 2, QTableWidgetItem(str(row.get("type"))))
            self.ui.uTable.setItem(i, 3, QTableWidgetItem(str(row.get("mode"))))
            self.ui.uTable.setItem(i, 4, QTableWidgetItem(str(row.get("host"))))
            self.ui.uTable.setItem(i, 5, QTableWidgetItem(str(row.get("port"))))
            self.ui.uTable.setItem(i, 6, QTableWidgetItem(str(row.get("username"))))
            self.ui.uTable.setItem(i, 7, QTableWidgetItem(str(row.get("password"))))
            self.ui.uTable.setItem(i, 8, QTableWidgetItem(str(row.get("database"))))
            self.ui.uTable.setItem(i, 9, QTableWidgetItem(str(row.get("dsn"))))
            self.ui.uTable.setItem(i, 10, QTableWidgetItem(str(row.get("mincached"))))
            self.ui.uTable.setItem(i, 11, QTableWidgetItem(str(row.get("maxconnections"))))
            self.ui.uTable.setItem(i, 12, QTableWidgetItem(str(row.get("maxcached"))))
            self.ui.uTable.setItem(i, 13, QTableWidgetItem(str(row.get("maxshared"))))
            self.ui.uTable.setItem(i, 14, QTableWidgetItem(str(row.get("databasepath"))))
            self.ui.uTable.setItem(i, 15, QTableWidgetItem(str(row.get("format"))))
            self.ui.uTable.setItem(i, 16, QTableWidgetItem(str(row.get("left"))))
            self.ui.uTable.setItem(i, 17, QTableWidgetItem(str(row.get("right"))))
            self.ui.uTable.setItem(i, 18, QTableWidgetItem(str(row.get("mssql_version"))))
        # 去空值
        _ = [[self.ui.uTable.item(i, cols).setText("") if self.ui.uTable.item(i,
                                                                              cols).text() == "None" else None
              for cols in range(len(keys))] for i in range(rows)]
        # 居中
        _ = [[self.ui.uTable.item(i, cols).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) for cols in
              range(len(keys))] for i in range(rows)]
        return connectList

    # 存储更新数据库
    @pyqtSlot(bool)
    def on_uSaveConnectConfig_clicked(self):
        fields = (
                "name", "TagSource", "type", "mode", "host", "port", "username", "password", "database", "dsn",
                "mincached", "maxcached", "maxconnections", "maxshared", "databasepath", "format", "left", "right",
                "mssql_version")
        values = [
                self.ui.uDBID.text(),
                self.ui.uDBTagSource.currentText(),
                self.ui.uDBType.currentText(),
                self.ui.uDBPool.currentText(),
                self.ui.uDBHost.text(),
                self.ui.uDBPort.text(),
                self.ui.uDBUser.text(),
                self.ui.uDBPassword.text(),
                self.ui.uDBName.text(),
                self.ui.uDSN.text(),
                self.ui.uPoolMincached.value(),
                self.ui.uPoolMaxcached.value(),
                self.ui.uPoolMaxconnections.value(),
                self.ui.uPoolMaxshared.value(),
                self.get_file_path(),
                self.ui.uFormat.text(),
                1 if self.ui.uLeft.isChecked() else 0,
                1 if self.ui.uRight.isChecked() else 0,
                self.ui.mssql_version.currentText()
                ]
        if self.connectList.get(values[0]):
            res = self.set_conn_config(fields, values, True)
            if res:
                self.log.info("更新数据库连接：{}，成功".format(values[0]))
        else:
            res = self.set_conn_config(fields, values)
            if res:
                self.log.info("增加新数据库连接：{}，成功".format(values[0]))
        self.get_conn_list()

    # 设置连接配置
    def set_conn_config(self, field, value, update = None):

        if update:
            setValue = ",".join(["{}='{}'".format(key, value[1:][i]) for i, key in enumerate(field[1:])])
            sql = "UPDATE ConnectConfig SET {} WHERE name='{}'".format(setValue, value[0])
            self.loacl_connect.execSql(sql)
        else:
            fields = ",".join(field)
            values = ",".join(["'{}'".format(v) for v in value])
            sql = "INSERT INTO ConnectConfig ({}) VALUES({})".format(fields, values)
            self.loacl_connect.execSql(sql)
        self.log.info("增加或修改数据库连接配置...")

    # 删除连接
    @pyqtSlot(bool)
    def on_uDelConnectConfig_clicked(self):
        try:
            name = self.ui.uDBID.text()
            ok = show_message("确认需要删除：{} 的连接配置嘛？".format(name))
            if not ok:
                return
            result = self.loacl_connect.execSql("DELETE FROM ConnectConfig WHERE name='{}'".format(name))
            if result:
                self.log.warning("删除连接：{},成功".format(name))
            if result == 0:
                self.log.warning("删除连接：{},失败，请确认有此连接".format(name))
            self.get_conn_list()
        except Exception as e:
            self.log.error("异常：{}".format(e))

    @staticmethod
    def show_message(info):
        return QMessageBox.information(None, "提示", info, QMessageBox.Yes | QMessageBox.No)

    # 选择数据库连接显示
    @pyqtSlot(QTableWidgetItem)
    def on_uTable_itemClicked(self, item):
        rows = item.row()
        self.ui.uDBID.setText(self.ui.uTable.item(rows, 0).text())
        self.ui.uDBTagSource.setCurrentText(self.ui.uTable.item(rows, 1).text())
        self.ui.uDBType.setCurrentText(self.ui.uTable.item(rows, 2).text())
        self.ui.uDBPool.setCurrentText(self.ui.uTable.item(rows, 3).text())
        self.ui.uDBHost.setText(self.ui.uTable.item(rows, 4).text())
        self.ui.uDBPort.setText(self.ui.uTable.item(rows, 5).text())
        self.ui.uDBUser.setText(self.ui.uTable.item(rows, 6).text())
        self.ui.uDBPassword.setText(self.ui.uTable.item(rows, 7).text())
        self.ui.uDBName.setText(self.ui.uTable.item(rows, 8).text())
        self.ui.uDSN.setText(self.ui.uTable.item(rows, 9).text())
        self.ui.uPoolMincached.setValue(int(self.ui.uTable.item(rows, 10).text()))
        self.ui.uPoolMaxconnections.setValue(int(self.ui.uTable.item(rows, 11).text()))
        self.ui.uPoolMaxcached.setValue(int(self.ui.uTable.item(rows, 12).text()))
        self.ui.uPoolMaxshared.setValue(int(self.ui.uTable.item(rows, 13).text()))
        self.ui.uFilePath.setText(self.ui.uTable.item(rows, 14).text())
        self.ui.uFormat.setText(self.ui.uTable.item(rows, 15).text())
        self.ui.uLeft.setChecked(eval(self.ui.uTable.item(rows, 16).text()))
        self.ui.uRight.setChecked(eval(self.ui.uTable.item(rows, 17).text()))
        self.ui.mssql_version.setCurrentText(self.ui.uTable.item(rows, 18).text() if self.ui.uTable.item(rows, 18) else "" )

    # 获取文件路径
    def get_file_path(self):
        left = self.ui.uLeft.isChecked()
        timeFormat = self.ui.uFormat.text()
        filePath = self.ui.uFilePath.text()
        if timeFormat and filePath:
            fileDir, name = os.path.split(filePath)
            fileName, ext = os.path.splitext(name)
            # 替换库名前后有可能的数字
            fileName = re.sub('^\d+', "", fileName) if left else re.sub('\d+$', "", fileName)
            # 根据格式获取时间值
            extValue = timeEXT.FormatStr(timeEXT.GetUtc(), formatStr = timeFormat)
            newName = extValue + fileName + ext if left else fileName + extValue + ext
            newPath = os.path.normpath(os.path.join(fileDir, newName))
            return newPath
        return filePath

    # 设置显示app配置
    def load_local_config(self):
        data = self.loacl_connect.loadSql("SELECT * FROM APPConfig", 1)[0]
        self.ui.uAPPID.setText(data.get("APPID", "01"))
        self.ui.uDevideID.setText(data.get("DeviceID", ""))
        self.ui.APPName.setText(data.get("APPName", ""))
        self.ui.start_sdk.setChecked(data.get("StartSDK", 0))
        self.ui.start_s7.setChecked(data.get("StartS7", 0))
        self.ui.start_password.setChecked(data.get("EnablePassword", 0))
        self.password_enable = data.get("EnablePassword", 0)
        self.ui.uAPPID.textChanged.connect(lambda v: self.set_app_config("APPID", v))
        self.ui.uDevideID.textChanged.connect(lambda v: self.set_app_config("DeviceID", v))
        self.ui.APPName.textChanged.connect(lambda v: self.set_app_config("APPName", v))

        return data

    # 设置app
    def set_app_config(self, field, value):
        if field == 'APPName':
            self.app_name_signal.emit(value)
        sql = "UPDATE APPConfig SET {}='{}'".format(field, value)
        return self.loacl_connect.execSql(sql)

    # 初始流水序号
    @pyqtSlot(bool)
    def on_uInitSerialNumber_clicked(self):
        ok = show_message("请确认是否清除存储流水序号记录，清除后，流水从1开始。。。")
        if ok:
            result = self.loacl_connect.execSql("DELETE FROM LastMark")
            if result:
                show_message("清除流水序号成功")

    # 设置显示日志
    def get_log_config(self):
        data = self.loacl_connect.loadSql("SELECT * FROM LogConfig", 1)[0]
        self.ui.log_level.setCurrentIndex(data.get("LogLevel") / 10 - 1)
        self.ui.uLogPath.setText(data.get("LogSavePath"))
        self.ui.uLogNumber.setValue(data.get("LogSaveNumber"))
        self.ui.uLogSize.setValue(data.get("LogSaveSize"))
        self.ui.log_level.currentTextChanged.connect(lambda v: self.set_log_config("Loglevel", v))
        self.ui.uLogPath.textChanged.connect(lambda v: self.set_log_config("LogSavePath", v))
        self.ui.uLogNumber.valueChanged.connect(lambda v: self.set_log_config("LogSaveNumber", v))
        self.ui.uLogSize.valueChanged.connect(lambda v: self.set_log_config("LogSaveSize", v))
        self.ui.uOpenLogPath.clicked.connect(
                lambda: self.ui.uLogPath.setText(QFileDialog.getExistingDirectory(self, "选择文件夹", "./")))
        return data

    def set_log_config(self, field, value):
        sql = "UPDATE LogConfig SET {}='{}'".format(field, value)
        return self.loacl_connect.execSql(sql)

    # 打开库路径
    @pyqtSlot(bool)
    def on_uOpenFilePath_clicked(self):
        self.ui.uFilePath.setText(
                QFileDialog.getOpenFileName(self, '打开数据库文件', './', 'files (*.mdb *.db *.accdb)')[0])


if __name__ == '__main__':
    # import localDB

    log = loadLog.Logger(name = 'main')
    config = {'databasepath': './app/config.db', 'type': 'sqlite'}
    conn = connect_database.Connect(**config)
    app = QApplication(sys.argv)
    gui = Window(conn)
    gui.show()
    sys.exit(app.exec_())